﻿package {	import flash.net.*;		import flash.system.ApplicationDomain;		import br.com.stimuli.loading.BulkLoader;	import br.com.stimuli.loading.BulkProgressEvent;	import br.com.stimuli.loading.BulkErrorEvent;		import flash.utils.Dictionary;		import flash.display.*;	import flash.text.TextField;	import flash.text.TextFieldAutoSize;	import flash.text.TextFormat;	import flash.display.StageAlign;	import flash.display.StageScaleMode;	import flash.geom.Rectangle;	import flash.events.TimerEvent;	import flash.utils.Timer;	import flash.geom.Point;		import flash.events.KeyboardEvent;	import flash.ui.Keyboard;		import flash.display.Sprite;	import flash.events.Event;	import flash.events.ProgressEvent;	import flash.events.MouseEvent;    import mx.events.MetadataEvent;    import mx.events.VideoEvent;		//import org.papervision3d.cameras.Camera3D;	import zephyr.cameras.PSCamera3D;	import zephyr.objects.primitives.Cube;	import zephyr.objects.primitives.Cylinder;	import org.papervision3d.objects.primitives.Plane;	import zephyr.objects.primitives.Sphere;	//import org.papervision3d.render.BasicRenderEngine;	import zephyr.render.PSRenderEngine;	import org.papervision3d.scenes.Scene3D;	import org.papervision3d.view.Viewport3D;	import org.papervision3d.events.InteractiveScene3DEvent;	import org.papervision3d.objects.DisplayObject3D;	import org.papervision3d.materials.BitmapMaterial;	import org.papervision3d.materials.utils.PrecisionMode;	import org.papervision3d.materials.utils.MaterialsList;	import org.papervision3d.core.proto.MaterialObject3D;	import org.papervision3d.events.RendererEvent;	import org.papervision3d.core.render.data.RenderStatistics;	import org.papervision3d.core.math.Number3D;	import org.papervision3d.view.layer.ViewportBaseLayer;	import org.papervision3d.view.layer.ViewportLayer;	import org.papervision3d.materials.MovieMaterial;	import com.eyesee360.materials.VideoPlayerMaterial;		import mx.core.Singleton;    import mx.resources.ResourceManager;	import com.eyesee360.VideoDisplaySprite;	import flash.net.NetStream;	import flash.net.NetConnection;	import flash.system.Security;		//import gs.TweenFilterLite;	import gs.TweenLite;	import gs.easing.*;		import com.adobe.utils.StringUtil;	import flash.utils.*;		import zephyr.objects.StageAlignedSprite;	import zephyr.objects.Hotspot;	import zephyr.objects.primitives.GeodesicSphere;	import zephyr.BroadcastEvent;		import org.papervision3d.view.stats.StatsView;		import zephyr.utils.StringTo;		/** 	* The PanoSalado Class is used to display panoramas in cubic (or equirectangular, cylindrical, or QTVR format).  	* It is primarily meant to be used in conjunction with ModuleLoader.as, with ModulueLoader loading the XML descriptor file	* and then loading the compiled PanoSalado.swf file and any other swfs to be loaded as specified in the XML descriptor.	*/	public class PanoSalado extends Sprite	{		/**		* Bulkloader class that PanoSalado uses for loading assets (images)		*/		public var bulkLoader : BulkLoader;				//		public var ui:Sprite = new Sprite();				/**		* Array of objects containing the basic PV3D objects for creating a "space", Scene, Camera, Renderer, etc		*/		public var spaces:Array = new Array();				/**		* primary container for the viewports so that they can be isolated from the rest of the display list for easier access.		*/		public var viewports:Sprite = new Sprite();				/**		* name of the currentSpace as specified in the XML.		*/		public var currentSpace:String = "";				/**		* reference to the last space that was loaded.		*/		public var lastSpace:String = "";				/**		* name of the space that is currently loading if applicable.		*/		public var loadingSpace:String = "";				/**		* reference to the current VideoDisplaySprite.		*/		public var currentVideo:VideoDisplaySprite;				/**		* Object containing the string equivalents between XML event listener types and PanoSalado's type names.		*/		private var interactionEquivalents:Object = { mouseClick:"onClick", mouseOver:"onOver", mouseOut:"onOut", mousePress:"onPress", mouseRelease:"onRelease", mouseMove:"onMouseMove", mouseDown:"onPress", mouseUp:"onRelease", click:"onClick" };				/**		* storage for assets that have been loaded before all assets are loaded and claimed.		*/		private var unclaimedMaterials:Dictionary = new Dictionary(true);				/**		* flag to control whether or not to render the scene.		*/		private var _worldDirty:Boolean = false;				/**		* reference to the XML object containing PanoSalado's XML layer node (layer id="PanoSalado")		*/		public var settings : XML;		//		public var loadMeter:Sprite = new Sprite();				/**		* objects that need to have their placement changed on stage resize.		*/		public var resizeDict:Object = new Object();				/**		* reference to the ModuleLoader singleton.		*/		private var moduleLoader:Object;				private var ModuleLoader:Class;		//		private var BroadcastEvent:Class;				/**		* junk.		*/		private var CustomActions:Class;				/**		* used to track QTVR loading.		*/		private var qtvrCount:int = 0;				/**		* constructor		*/		public function PanoSalado() 		{		    // Workaround for Flex 3 framework bug.// 		    var resourceManagerImpl:Object = //             flash.system.ApplicationDomain.currentDomain.getDefinition("mx.resources::ResourceManagerImpl"); //             mx.core.Singleton.registerClass("mx.resources::IResourceManager", //             Class(resourceManagerImpl));             			addEventListener(Event.ADDED_TO_STAGE, stageReady, false, 0, true);						//add viewports sprite to display list.  It will hold all the viewports so they can be iterated/isolated from other items			addChild(viewports);						addEventListener(Event.ENTER_FRAME, doRender, false, 0, true);						//set up bulk loader			bulkLoader = new BulkLoader("panoSaladoBulkLoader");			bulkLoader.addEventListener(BulkProgressEvent.PROGRESS, onAllProgress, false, 0, true);			bulkLoader.addEventListener(BulkLoader.COMPLETE, onAllLoaded, false, 99, true);			// 			addChild(ui);// 			ui.addEventListener(MouseEvent.CLICK, mouseEventHandler, false, 0, true);// 			ui.addEventListener(MouseEvent.MOUSE_OVER, mouseEventHandler, false, 0, true);// 			ui.addEventListener(MouseEvent.MOUSE_OUT, mouseEventHandler, false, 0, true);// 			ui.addEventListener(MouseEvent.MOUSE_DOWN, mouseEventHandler, false, 0, true); // 			ui.addEventListener(MouseEvent.MOUSE_UP, mouseEventHandler, false, 0, true); // 			ui.addEventListener(MouseEvent.MOUSE_MOVE, mouseEventHandler, false, 0, true); 		}				/**		* stage ready is called when ModuleLoader has added PanoSalado to the stage.  		* This is where PanoSalado sets stage properties: framerate quality and a resize event.		*/		public function stageReady(e:Event):void		{			stage.frameRate = 30;			stage.quality = StageQuality.HIGH;			stage.addEventListener(Event.RESIZE, onResize, false, 100, true);						var doLoadSettings:Boolean = true;    		if (ApplicationDomain.currentDomain.hasDefinition("ModuleLoader"))     		{    			ModuleLoader = ApplicationDomain.currentDomain.getDefinition("ModuleLoader") as Class;    			//trace(ModuleLoader);    		    moduleLoader = ModuleLoader.moduleLoader;    		    if (moduleLoader)     		    {       				moduleLoader.addEventListener(BroadcastEvent.ALL_LAYERS_LOADED, allLayersLoaded, false, 0, true);				        			if ( moduleLoader.xmlByName["PanoSalado"] )         			{            			doLoadSettings = false;        				settings = moduleLoader.xmlByName["PanoSalado"];        				onXMLLoaded();    			    }    		    }            }            			if ( doLoadSettings ) 			{				var xmlLoader:URLLoader = new URLLoader();				xmlLoader.dataFormat = URLLoaderDataFormat.BINARY;				xmlLoader.load( new URLRequest( loaderInfo.parameters.xml?loaderInfo.parameters.xml:"PanoSalado.xml" ) );				xmlLoader.addEventListener(Event.COMPLETE, onXMLLoaded, false, 0, true);		    }		}				/**		* this should be triggered when ModuleLoader dispatched the allLayersLoaded event.		*/		private function allLayersLoaded(e:*):void		{			e = e as BroadcastEvent;						if ( ApplicationDomain.currentDomain.hasDefinition("CustomActions") )				CustomActions = ApplicationDomain.currentDomain.getDefinition("CustomActions") as Class;		}				/**		* used by an alternate setup whereby PanoSalado is used standalone and loads its own XML		*/		private function onXMLLoaded(e:Event=null):void		{			if (e)				settings = XML(e.target.data);						if (settings.spaces.@stageQuality.length != 0)			{				switch(settings.spaces.@stageQuality) 				{					case "low":						stage.quality = StageQuality.LOW;    						break;					case "medium":						stage.quality = StageQuality.MEDIUM;    						break;					case "high":					default:						stage.quality = StageQuality.HIGH;    						break;					case "best":						stage.quality = StageQuality.BEST;    						break;				}			}						initCameraController( true, int(settings.spaces.@autorotatorDelay) || 15000 );						refresh();						//onStart code hook, pass second arg false to NOT check for onStart in non-existent current scene node.			XMLCodeHook("onStart", false);					}				private function vfovForFocusZoom(focus:Number, zoom:Number):Number		{		    // !!! Need to get the stage height 		    var height:Number = 480; // viewports[0].viewportHeight;		    return 2.0 * Math.atan2( 0.5 * height, zoom * focus );	    }				/**		* refreshes values that should usually only change each time a new space is loaded.		* this avoids unnecessary XML access which is slow for frequently consulted vars like camera limits etc.		*/		private function refresh():void		{			sensitivity = 	findValueInXML("cameraSensitivity", Number, 	60);			friction = 		findValueInXML("cameraFriction", Number, 0.3);			threshold = 	findValueInXML("cameraRestThreshold", Number, 0.0001);			keyIncrement = 	findValueInXML("cameraKeyIncrement", Number, 75);			vfovIncrement = findValueInXML("cameraVFOVIncrement", Number, 2.0);						maxTilt = findValueInXML("cameraMaximumTilt", Number, 9999);			minTilt = findValueInXML("cameraMinimumTilt", Number, 9999);						maxPan = findValueInXML("cameraMaximumPan", Number, 9999);			minPan = findValueInXML("cameraMinimumPan", Number, 9999);						minVFOV = findValueInXML("cameraMinimumVFOV", Number, 10);			maxVFOV = findValueInXML("cameraMaximumVFOV", Number, 100);						// focus/zoom compatibility			var focus:Number = 100.0;			var zoomIncrement:Number = findValueInXML("cameraZoomIncrement", Number, 0.0);			var minZoom:Number = findValueInXML("cameraMinimumZoom", Number, 0);			var maxZoom:Number = findValueInXML("cameraMaximumZoom", Number, 0);			if (zoomIncrement != 0.0) vfovIncrement = vfovForFocusZoom(focus, zoomIncrement);		    if (minZoom != 0.0) minVFOV = vfovForFocusZoom(focus, minZoom);		    if (maxZoom != 0.0) maxVFOV = vfovForFocusZoom(focus, maxZoom);						dqa = findValueInXML("dynamicQualityAdjustment", Boolean, true) ;						accSmooth =  findValueInXML("smoothOnAcceleration", Boolean, false );			accPrecise =  findValueInXML("preciseOnAcceleration", Boolean, true );			accPrecision = findValueInXML("precisionOnAcceleration", int, 32);						decSmooth =  findValueInXML("smoothOnDeceleration", Boolean, true );			decPrecise =  findValueInXML("preciseOnDeceleration", Boolean, true );			decPrecision = findValueInXML("precisionOnDeceleration", int, 8);						stopSmooth =  findValueInXML("smoothAtRest", Boolean, true) ;			stopPrecise =  findValueInXML("preciseAtRest", Boolean, true );			stopPrecision = findValueInXML("precisionAtRest", int, 1);						aah = findValueInXML("autorotatorAutoHorizon", Boolean, true);			da = findValueInXML("autorotatorIncrement", Number, 0.25);			aaz = findValueInXML("autorotatorAutoZoom", Boolean, true);		}				/**		* puts each asset as it has loaded into the unclaimedMaterials dictionary.		*/				private function onSingleItemLoaded(e:Event):void		{						var bm:BitmapMaterial = new BitmapMaterial( BitmapData(e.target._content.bitmapData) );			unclaimedMaterials[ e.target.url.url.toString() ] = bm ;		}		/**		* prepares stream materials		*/		private function onStreamMaterialLoaded(e:Event):void		{		    var vm:VideoPlayerMaterial = e.target.material;		    		    unclaimedMaterials[ e.target.source ] = vm;		    currentVideo = VideoDisplaySprite(e.target);						onAllLoaded();			dispatchEvent(new Event("videoLoaded"));	    }				/**		* handles PHP QTVR parser script XML output		*/		private function qtvrXMLLoaded(e:Event):void		{			qtvrCount -= 1;			for each (var mov:XML in findXMLNode(loadingSpace)..mov)			{				var id:String = e.target.url.url.toString();				if (mov == id )				{					var target:XML = mov.parent(); 										var xml:XML = bulkLoader.getXML(id);										if( StringTo.bool(target.@applyCameraSettingsFromThisQTVR) )					{						target.parent().@cameraMinimumPan = xml.@cameraMinimumPan;						target.parent().@cameraMaximumPan = xml.@cameraMaximumPan;						target.parent().@cameraMinimumTilt = xml.@cameraMinimumTilt;						target.parent().@cameraMaximumTilt = xml.@cameraMaximumTilt;						target.parent().@cameraMinimumVFOV = xml.@cameraMinimumVFOV;						target.parent().@cameraMaximumVFOV = xml.@cameraMaximumVFOV;						target.parent().@cameraPan = xml.@cameraPan;						target.parent().@cameraTilt = xml.@cameraTilt;						target.parent().@cameraVFOV = xml.@cameraVFOV;					}										target.setChildren( xml.children() );										for each (var tile:XML in target.tile)					{ 						bulkLoader.add(tile.toString(), { type:"image", weight: (tile.@weight || 10) });					}										bulkLoader.start();				}			}		}				/**		* bulkLoader's progress handler. dispatches progress events on ModuleLoader.		*/		private function onAllProgress(e : BulkProgressEvent) : void		{		 	//trace("progress event: loaded" , e.bytesLoaded," of ",  e.bytesTotal);		 	moduleLoader.dispatchEvent( new BroadcastEvent(BroadcastEvent.LOAD_PROGRESS, { id : bulkLoader.name, percentLoaded : e.weightPercent }) );		}				/**		* @private		*/		private var autorotatorFirstRun:Boolean = false;				/**		* main function that handles setting up a new space		*/		private function onAllLoaded(e : BulkProgressEvent=null) : void		{			if ( qtvrCount != 0 ) 				return;						trace("PS: initializing " + currentSpace );						lastSpace = currentSpace;						currentSpace = loadingSpace;						loadingSpace = "";						dispatchBroadcast(BroadcastEvent.SPACE_LOADED, "spaceLoaded="+currentSpace);						refresh();						// create a new space (viewport, scene, and camera)			var idx:int = instantiateNewSpace();						var thisSpace:Object = spaces[idx];						//set up viewport			setupViewport( thisSpace.viewport );						//set-up camera			setupCamera(thisSpace.camera, spaces[idx-1]);						//iterate through all the objects in the scene (pano, hotspots, etc)			var sceneObjects:XML = findXMLNode(currentSpace);			if (sceneObjects) for each (var xml:XML in sceneObjects.children() )			{				var nodeName:String = xml.name().localName.toString();								//create papervision primitive by calling the function bearing its name (cube, plane, sphere, etc)				if ( hasOwnProperty(nodeName) )					var primitive:Object = this[ nodeName ].call(null, xml);				else					continue;				//				if ( nodeName != "stageAlignedSprite" )//				{					primitive.name = xml.@id.toString();										//set position and rotation of primitive, using += so that it can be pre adjusted e.g. sphere					primitive.x += int(xml.@x) || 0;					primitive.y += int(xml.@y) || 0;					primitive.z += int(xml.@z) || 0;										primitive.rotationX += Number(xml.@rotationX) || 0;					primitive.rotationY += Number(xml.@rotationY) || 0; 					primitive.rotationZ += Number(xml.@rotationZ) || 0; 										primitive.visible = StringTo.bool(xml.@visible || "true");					primitive.useOwnContainer = StringTo.bool(xml.@useOwnContainer || "false");										if ( primitive.useOwnContainer )					{						primitive.alpha = Number(xml.@alpha) || 1 ;						primitive.blendMode = xml.@blendMode.toString() || "normal";						//primitive.filters = ;					}										if ( xml.@onClick.toString() != "" ) { primitive.addEventListener(InteractiveScene3DEvent.OBJECT_CLICK, interactionScene3DEventHandler, false, 0, true); }					if ( xml.@onOver.toString() != "" ) { primitive.addEventListener(InteractiveScene3DEvent.OBJECT_OVER, interactionScene3DEventHandler, false, 0, true); }					if ( xml.@onOut.toString() != "" ) { primitive.addEventListener(InteractiveScene3DEvent.OBJECT_OUT, interactionScene3DEventHandler, false, 0, true); }					if ( xml.@onPress.toString() != "" ) { primitive.addEventListener(InteractiveScene3DEvent.OBJECT_PRESS, interactionScene3DEventHandler, false, 0, true); }					if ( xml.@onRelease.toString() != "" ) { primitive.addEventListener(InteractiveScene3DEvent.OBJECT_RELEASE, interactionScene3DEventHandler, false, 0, true); }					if ( xml.@onOverMove.toString() != "" ) { primitive.addEventListener(InteractiveScene3DEvent.OBJECT_MOVE, interactionScene3DEventHandler, false, 0, true); }										if (StringTo.bool( xml.@useHandCursor || "false") )					{						primitive.addEventListener(InteractiveScene3DEvent.OBJECT_OVER, cursorHandler, false, 0, true);						primitive.addEventListener(InteractiveScene3DEvent.OBJECT_OUT, cursorHandler, false, 0, true);					}										if ( xml.@toolTip.toString() != "" )					{						primitive.addEventListener(InteractiveScene3DEvent.OBJECT_OVER, tooltipHandler, false, 0, true);						primitive.addEventListener(InteractiveScene3DEvent.OBJECT_OUT, tooltipHandler, false, 0, true);					}										primitive.updateTransform();										thisSpace.scene.addChild( primitive, xml.@id.toString() );//				}//				else //				{//					ui.addChild( StageAlignedSprite(primitive) );//				}			}						//onResize();						bulkLoader.removeAll();												// add viewport to the displaylist			viewports.addChild( thisSpace["viewport"] );									//transitionStart code hook			XMLCodeHook("onTransitionStart");						var trans:String = findValueInXML("transition", String, "tween:currentSpace.viewport.alpha from 0 over 1.5 seconds using Expo.easeIn");						// old onTransitionEnd handling code. May disable once replacement is implemented.			if (true) {				var idxThenDo:int = trans.indexOf("then do ");								if ( idxThenDo == -1 )				{					if ( trans.lastIndexOf(";")+1 == trans.length)						trans = trans.substr(0, trans.length-1) + " then do onTransitionEnd";					else						trans = trans + " then do onTransitionEnd";				}				else				{					if ( trans.lastIndexOf(";")+1 == trans.length)						trans = trans + " onTransitionEnd";					else						trans = trans + "; onTransitionEnd";				}			}			execute( trans );						_worldDirty = true;									//code hook			XMLCodeHook("onDisplay");						//remove onDisplay so it doesn't trigger again			settings.@onDisplay = "";						///autorotator			if ( findValueInXML("autorotator", Boolean, true) )			{				_autorotatorOn = true;								if (autorotatorFirstRun)					restartAutorotatorTimer( findValueInXML("autorotatorDelay", Number, 15000) );				else					restartAutorotatorTimer( findValueInXML("autorotatorRestartDelay", Number, 15000) );								autorotatorFirstRun = false;			}			else			{				_autorotatorOn = false;								stopAutorotatorNow();			}					}				/**		* helper function that handles setting up viewport properties with XML values.		*/		private function setupViewport(viewport:Viewport3D):void		{			viewport.interactive = findValueInXML("interactive", Boolean, true);			viewport.autoScaleToStage = findValueInXML("viewportAutoScaleToStage", Boolean, true );			viewport.autoCulling = findValueInXML("viewportAutoCulling", Boolean, true );			viewport.autoClipping = findValueInXML("viewportAutoClipping", Boolean, true );			viewport.viewportWidth = findValueInXML("viewportWidth", Number, 640 );			viewport.viewportHeight = findValueInXML("viewportHeight", Number, 480 );			viewport.x = findValueInXML("viewportX", Number, 0 );			viewport.y = findValueInXML("viewportY", Number, 0 );		}				/**		* helper function that handles setting up the camera properties with XML values.		*/		private function setupCamera(camera:PSCamera3D, lastSpace:Object=null):void		{			// set up camera: 			var cameraContinuity:Boolean = findValueInXML("cameraRetainsLastValues", Boolean, false);			var pan:Number = findValueInXML("cameraPan", Number, 0 );			var tilt:Number = findValueInXML("cameraTilt", Number, 0);			var vfov:Number = findValueInXML("cameraVFOV", Number, 60);			var camX:Number = findValueInXML("cameraX", Number, 0);			var camY:Number = findValueInXML("cameraY", Number, 0);			var camZ:Number = findValueInXML("cameraZ", Number, 0);			// focus/zoom compatibility			var zoom:Number = findValueInXML("cameraZoom", Number, 0); 			var focus:Number = findValueInXML("cameraFocus", Number, 100);		    if (zoom != 0.0) vfov = vfovForFocusZoom(focus, zoom);						// leash free or default unspecified leashing			if (!cameraContinuity)			{				camera.rotationX = tilt;				camera.rotationY = pan;				camera.rotationZ = 0;				camera.fov = vfov;				camera.x = camX;				camera.y = camY;				camera.z = camZ;				}			else			{				//leash = lock				camera.rotationX = lastSpace != null ? lastSpace["camera"].rotationX : tilt ;				camera.rotationY = lastSpace != null ? lastSpace["camera"].rotationY : pan ;				camera.rotationZ = lastSpace != null ? lastSpace["camera"].rotationZ : 0 ;				camera.fov = lastSpace != null ? lastSpace["camera"].fov : vfov;				camera.x = lastSpace != null ? lastSpace["camera"].x : camX ;				camera.y = lastSpace != null ? lastSpace["camera"].y : camY ;				camera.z = lastSpace != null ? lastSpace["camera"].z : camZ ;			}			constrainCamera(camera);		}				/**		* helper function that takes a loaded asset and converts it into a BitmapMaterial for PV3d		*/		private function createMaterial(xml:XML):*		{			var returnObject:*;			var materials:MaterialsList = new MaterialsList();						if (xml.file != undefined)			{ // BitmapMaterial				var numFileMaterials:int = xml.file.length();								for each (var file:XML in xml.file)				{					var material:BitmapMaterial =  BitmapMaterial(unclaimedMaterials[file.toString()]);										applyPropertiesToMaterial(material, xml);										if ( numFileMaterials > 1 )					{						materials.addMaterial( material, file.@face.toString() );					}				}								if ( numFileMaterials > 1 )					returnObject = materials;				else					returnObject = material;			}			if (xml.stream != undefined)			{ // VideoStreamMaterial				var numStreamMaterials:int = xml.stream.length();								for each (var stream:XML in xml.stream)				{					var streamMaterial:VideoPlayerMaterial =  VideoPlayerMaterial(unclaimedMaterials[stream.toString()]);										applyPropertiesToMaterial(streamMaterial, xml);										if ( numStreamMaterials > 1 )					{						materials.addMaterial( streamMaterial, stream.@face.toString() );					}				}								if ( numFileMaterials > 1 )					returnObject = materials;				else					returnObject = streamMaterial;			}						return returnObject;		}				/**		* helper function that applies properties to material from XML values.		*/		private function applyPropertiesToMaterial(material:BitmapMaterial, xml:XML):BitmapMaterial		{			if ( findValueInXML("dynamicQualityAdjustment", Boolean, true) )			{				material.smooth =  findValueInXML("smoothAtRest", Boolean, true  ) ;								material.precise =  findValueInXML("preciseAtRest", Boolean, true  ) ;							material.precision = findValueInXML("precisionAtRest", int, 1) ;								material.precisionMode = PrecisionMode.STABLE;			}			else			{				material.smooth = StringTo.bool( xml.@smooth || "false") ;									material.precise = StringTo.bool( xml.@precise || "false") ;								material.precision = int( xml.@precision || 8) ;								material.precisionMode = PrecisionMode.STABLE;			}						material.oneSide = StringTo.bool( xml.@oneSide || "true") ;					material.interactive = StringTo.bool( xml.@interactive || "false") ;						return material;		}				/**		* creates a sphere.  not to be called directly.  in the descriptor XML, a 'sphere' node will cause this to be called.		*/		public function sphere(xml:XML):Object		{						var material:BitmapMaterial = createMaterial(xml) as BitmapMaterial;			//var material:BitmapMaterial =  BitmapMaterial(unclaimedMaterials[xml.file.toString()]);						var segments:int = int( xml.@segments) || 18 ;			var radius:int = int( xml.@radius ) || 50000;			var reverse:Boolean = StringTo.bool(xml.@reverse || "true");			var minPan:Number = Number( xml.@minPan ) || 0.0;			var panRange:Number = Number( xml.@panRange ) || 360.0;			var minTilt:Number = Number( xml.@minTilt ) || -90.0;			var tiltRange:Number = Number( xml.@tiltRange ) || 180.0;						var sphere:Sphere = new Sphere(material, radius, segments, segments, reverse, minPan, panRange, minTilt, tiltRange );						sphere.rotationY = 90 - 360/segments;						return sphere;		}				/**		* creates a geosphere.  not to be called directly.  in the descriptor XML, a 'geodesicSphere' node will cause this to be called.		*/		public function geodesicSphere(xml:XML):Object		{						var material:BitmapMaterial = createMaterial(xml) as BitmapMaterial;						//var material:BitmapMaterial =  BitmapMaterial(unclaimedMaterials[xml.file.toString()]);						var segments:int = int( xml.@segments) || 40 ;						var radius:int = int( xml.@radius ) || 50000;						var reverse:Boolean = StringTo.bool(xml.@reverse || "true");						//GeodesicSphere( material:MaterialObject3D=null, radius:Number=100, fractures:int=2, initObject:Object=null ) 			var geoSphere:GeodesicSphere = new GeodesicSphere(material, radius, segments, true );									geoSphere.rotationY = 90;									return geoSphere;		}				/**		* creates a cylinder.  not to be called directly.  in the descriptor XML, a 'cylinder' node will cause this to be called.		*/		public function cylinder(xml:XML):Cylinder		{			//Cylinder( material:MaterialObject3D=null, radius:Number=100, height:Number=100, segmentsW:int=8, segmentsH:int=6, topRadius:Number=-1, topFace:Boolean=true, bottomFace:Boolean=true )						var bmd:BitmapData = bulkLoader.getBitmapData(xml.file.toString(), false);						var material:BitmapMaterial = createMaterial(xml) as BitmapMaterial;						var segments:int = int( xml.@segments) || 30;						var radius:Number = Number( xml.@radius ) || 50000;						// Camera constraints can be different than model geometry			var cylMinTilt:Number = Number( xml.@minTilt ) || minTilt;			var cylMaxTilt:Number = cylMinTilt + Number( xml.@tiltRange ) || maxTilt;			 			if ( StringTo.bool(xml.@autoFOV) ) 			{ 			    var imgRadius:Number = bmd.width / (2 * Math.PI); 			    var halfT:Number = Math.atan2(bmd.height / 2, imgRadius) * 180/Math.PI; 				cylMinTilt = -halfT; 				cylMaxTilt = halfT; 			}                        var height:Number = radius * 2; // appropriate for a 90 deg tilt range            if (cylMaxTilt < 80.0 && cylMinTilt > -80.0) {                // prevent infinite height cylinders			    height = radius * (Math.tan(cylMaxTilt * Math.PI/180) - Math.tan(cylMinTilt * Math.PI/180));			}						var reverse:Boolean = StringTo.bool(xml.@reverse || "true");						var cyl:Cylinder = new Cylinder(material, radius, height, segments, segments, -1, false, false, reverse );						// We want the cylinder to be zeroed on the horizon.			cyl.y = height/2 + radius * Math.tan(cylMinTilt * Math.PI/180); 			cyl.rotationY = 0; 			cyl.rotationZ = 0; 						return cyl;		}				/**		* creates a cube.  not to be called directly.  in the descriptor XML, a 'cube' node will cause this to be called.		*/		public function cube(xml:XML):Cube		{			var materials:MaterialsList = createMaterial(xml) as MaterialsList;						var insideFaces  : int; 			if ( ! StringTo.bool(xml.@reverse) )				insideFaces	 = Cube.ALL;			else 				insideFaces	 = Cube.NONE;							var excludeFaces :int = Cube.NONE;						var segments:int = int( xml.@segments) || 9 ;						var width:int = int( xml.@width ) || 100000 ;						var normalCube:Cube = new Cube( materials, width, width, width, segments, segments, segments, insideFaces, excludeFaces );						return normalCube;		}				/**		* creates a hotspot.  not to be called directly.  in the descriptor XML, a 'hotspot' node will cause this to be called.		* note that a hotspot is only a special Plane which points toward the origin (where the camera is unless you move it).		*/		public function hotspot(xml:XML):Hotspot		{			var fileName:String = xml.file.toString();						if (fileName.search( /\.swf$/ ) == -1)				return bitmapHotspot(xml);			else				return movieHotspot(xml);		}				/**		* creates a bitmap hotspot.  Will be called by hotspot if the asset url does not end with '.swf'. it can be used directly with a bitmapHotspot node in the XML		*		*/		public function bitmapHotspot(xml:XML):Hotspot		{			var bmd:BitmapData = bulkLoader.getBitmapData(xml.file.toString(), false);						var width:Number = bmd.width * 40;						var height:Number = bmd.height * 40;						width = int( xml.@width) * 40 || width ;						height = int( xml.@height) * 40 || height ;						var material:BitmapMaterial = createMaterial(xml) as BitmapMaterial;						material.interactive = StringTo.bool( xml.@interactive) || true ;						var segments:int = int( xml.@segments) || 3 ;						var pan:Number = Number( xml.@pan) || 0 ;						var tilt:Number = Number( xml.@tilt) || 0 ; 						var hs:Hotspot = new Hotspot(pan, tilt, material, width, height, segments, segments );						return hs;		}				/**		* creates a movie hotsport.  Will be called automatically by hotspot if the asset url ends in '.swf'.  It can be called directly with a movieHotspot node in the XML.		*/		public function movieHotspot(xml:XML):Hotspot		{			var mc:MovieClip = bulkLoader.getMovieClip(xml.file.toString(), false);			var width:Number = mc.width * 40;						var height:Number = mc.height * 40;						width = int( xml.@width) * 40 || width ;						height = int( xml.@height) * 40 || height ;						var material:MovieMaterial = new MovieMaterial(mc, true);						material.interactive = StringTo.bool( xml.@interactive) || true ;						material.animated = StringTo.bool( xml.@animated) || true ;						var segments:int = int( xml.@segments) || 3 ;						var pan:Number = Number( xml.@pan) || 0 ;						var tilt:Number = Number( xml.@tilt) || 0 ; 						var hs:Hotspot = new Hotspot(pan, tilt, material, width, height, segments, segments );						return hs;		}				/**		* creates a plane.  not to be called directly.  in the descriptor XML, a 'plane' node will cause this to be called.		*/		public function plane(xml:XML):Plane		{			var width:Number = int( xml.@width) || 100 ;						var height:Number = int( xml.@height) || 100 ;						var material:MaterialObject3D = createMaterial(xml) as MaterialObject3D;						var segments:int = int( xml.@segments) || 3 ;						var plane:Plane = new Plane( material, width, height, segments, segments );						return plane;		}				/**		* creates a qtvr.  not to be called directly.  in the descriptor XML, a 'qtvr' node will cause this to be called.		*/		public function qtvr(xml:XML):Cube		{			var q:Cube = tiledCube(xml);						return q;		}				/**		* helper function for tiled QTVRs		*/		private function tiledCube(xml:XML):Cube		{			var bitmapDatasCreated:Boolean = false;						var bmd:BitmapData;						for each (var singleTile:XML in xml.tile)			{				bmd = bulkLoader.getBitmapData(singleTile.toString(), true) as BitmapData;								var w:Number = bmd.width;				var h:Number = bmd.height;								if ( ! bitmapDatasCreated)				{					var wTotal:Number = bmd.width * Math.sqrt( int( xml.tile.(@face == "front").length() ) )					var hTotal:Number = bmd.height * Math.sqrt( int( xml.tile.(@face == "front").length() ) )										var ft:BitmapData = new BitmapData(wTotal, hTotal, false, 0xFF0000);					var lt:BitmapData = new BitmapData(wTotal, hTotal, false, 0x00FF00);					var rt:BitmapData = new BitmapData(wTotal, hTotal, false, 0x0000FF);					var bk:BitmapData = new BitmapData(wTotal, hTotal, false, 0x888888);					var up:BitmapData = new BitmapData(wTotal, hTotal, false, 0xFFFFFF);					var dn:BitmapData = new BitmapData(wTotal, hTotal, false, 0x000000);										bitmapDatasCreated = true;				}								var rect:Rectangle = new Rectangle(0,0,w,h);				var pt:Point = new Point(w * int(singleTile.@x_offset), h * int(singleTile.@y_offset));								switch( singleTile.@face.toString() )				{					case "front":						ft.copyPixels(bmd,rect,pt);						break;					case "left":						lt.copyPixels(bmd,rect,pt);						break;					case "right":						rt.copyPixels(bmd,rect,pt);						break;					case "back":						bk.copyPixels(bmd,rect,pt);						break;					case "up":						up.copyPixels(bmd,rect,pt);						break;					case "down":						dn.copyPixels(bmd,rect,pt);						break;					default:						trace("invalid face value in " + xml.@id+ ", tile: "+ singleTile );				}			}						bmd.dispose();						var matsArray:Array = new Array();			matsArray.push( { id:"front", material:new BitmapMaterial(ft) } );			matsArray.push( { id:"left", material:new BitmapMaterial(lt) } );			matsArray.push( { id:"right", material:new BitmapMaterial(rt) } );			matsArray.push( { id:"back", material:new BitmapMaterial(bk) } );			matsArray.push( { id:"top", material:new BitmapMaterial(up) } );			matsArray.push( { id:"bottom", material:new BitmapMaterial(dn) } );						var materials:MaterialsList = new MaterialsList();						for each (var o:Object in matsArray)			{				if ( findValueInXML("dynamicQualityAdjustment", Boolean, true) )				{					o.material.smooth =  findValueInXML("smoothAtRest", Boolean, true  ) ;										o.material.precise =  findValueInXML("preciseAtRest", Boolean, true  ) ;									o.material.precision = findValueInXML("precisionAtRest", int, 1) ;				}				else				{					o.material.smooth = StringTo.bool( xml.@smooth || "false") ;											o.material.precise = StringTo.bool( xml.@precise || "false") ;										o.material.precision = int( xml.@precision) || 8 ;				}								o.material.oneSide = StringTo.bool( xml.@oneSide || "true") ;							o.material.interactive = StringTo.bool( xml.@interactive || "false") ;								materials.addMaterial( o.material, o.id );			}						var insideFaces  : int; 			if ( ! StringTo.bool(xml.@reverse) )				insideFaces	 = Cube.ALL;			else 				insideFaces	 = Cube.NONE;						var excludeFaces :int = Cube.NONE;						var segments:int = int( xml.@segments) || 9 ;						var width:int = int( xml.@width ) || 100000 ;						var cube:Cube = new Cube( materials, width, width, width, segments, segments, segments, insideFaces, excludeFaces );						return cube;		}				// 		private function stageAlignedSprite(xml:XML):StageAlignedSprite// 		{// 			var bm:BitmapData = bulkLoader.getBitmapData(xml.file.toString(), true);// 			// 			var sp:StageAlignedSprite = new StageAlignedSprite();// 			// 			sp.graphics.beginBitmapFill(bm, null, false, false);// 			// 			sp.graphics.drawRect(0,0,bm.width,bm.height);// 			// 			sp.graphics.endFill();// 			// 			sp.scaleX = Number(xml.@scaleX) || 1;// 			// 			sp.scaleY = Number(xml.@scaleY) || 1;// 			// 			sp.rotation = Number(xml.@rotation) || 0;// 			// 			sp.alpha = Number(xml.@alpha) || 1;// 			// 			sp.visible = StringTo.bool(xml.@visible || "true");// 			// 			sp.cacheAsBitmap = StringTo.bool(xml.@cacheAsBitmap || "false");// 			// 			sp.blendMode = String(xml.@blendMode) || "normal";// 			// 			sp.name = String(xml.@id) || "";// 			// 			sp.alignment = String(xml.@align) || "tl";// 			// 			sp.offsetX = Number(xml.@offsetX) || 0;// 			// 			sp.offsetY = Number(xml.@offsetY) || 0;// 			//sp.align();// 			return sp;// 		}								protected var dqa:Boolean,		accSmooth:Boolean,		accPrecise:Boolean,		accPrecision:int,		decSmooth:Boolean,		decPrecise:Boolean,		decPrecision:int,		stopSmooth:Boolean,		stopPrecise:Boolean,		stopPrecision:int;				/**		* handles changing the quality settings on the rendering based on mouse camera movement: accelerating, decelerating, stopped.		*/		private function changeQuality(type:String):void		{ 		/* change precise, smooth, and precision while moving camera for better fps		loops through all the scenes in all the spaces, getting all the objects from each, 		and then pushing either the materials from the materialsList, or the material into		an array, and then applies the changes to each item in the array.		*/			if ( dqa )			{				var allmats:Array = getAllMaterials();								var bmToChange:BitmapMaterial,				matToChange:MaterialObject3D;								while ( matToChange = allmats.pop() )				{					bmToChange = BitmapMaterial(matToChange);					if (type == "accelerating")					{						bmToChange.smooth = accSmooth;						bmToChange.precise = accPrecise;						bmToChange.precision = accPrecision;					}					else if (type == "decelerating")					{						bmToChange.smooth = decSmooth;						bmToChange.precise = decPrecise;						bmToChange.precision = decPrecision;					}					else if (type == "resting")					{						bmToChange.smooth = stopSmooth;						bmToChange.precise = stopPrecise;						bmToChange.precision = stopPrecision;																	}				}			}						//_worldDirty is set when camera or objects in scene have changed and need rendering.			_worldDirty = true;		}				/**		* gets all the materials used in a scene		* @return Array		*/		private function getAllMaterials():Array		{			var matsToChange:Array = new Array(),			matToChange:MaterialObject3D,			objsToChange:Array,			numObjsToChange:int,			objToChange:DisplayObject3D;						for (var i:int = 0; i < spaces.length; i++)			{				objsToChange = spaces[i].scene.objects as Array;				numObjsToChange = objsToChange.length;				for ( var j:int=0; j < numObjsToChange; j++ )				{					objToChange = DisplayObject3D(objsToChange[ j ]);										if (objToChange.materials)					{/// obj.materials is the materialsList, if it is not null add them						for each(  matToChange in objToChange.materials.materialsByName )						{							matsToChange.push(matToChange);						}					}					else 					{// obj.material is the material, add it						matsToChange.push( objToChange.material );					}				}			}			return matsToChange;		}				protected var dp:Number,		dt:Number,		maxTilt:Number,		minTilt:Number,		maxPan:Number,		minPan:Number,		cam:PSCamera3D,		newTilt:Number,		newPan:Number,		maxVFOV:Number,		minVFOV:Number;				/**		* updates the camera to constrain it to the present pan, tilt and zoom limits.		* @param camera		*/		private function constrainCamera(cam:PSCamera3D):void		{            if (maxVFOV == 9999) {                if (maxTilt != 9999 && minTilt != 9999) {                    maxVFOV = maxTilt - minTilt;                    if (maxVFOV > 100) maxVFOV = 100;                } else {                    maxVFOV = 100;                }            }            if (minVFOV == 9999) {                minVFOV = 10;            }                        if (cam.fov > maxVFOV) {                cam.fov = maxVFOV;            }            if (cam.fov < minVFOV) {                cam.fov = minVFOV;            }            if (minPan != 9999) {                if (cam.rotationY - cam.hfov*0.5 < minPan) {                    cam.rotationY = minPan + cam.hfov*0.5;                }            }            if (maxPan != 9999) {                if (cam.rotationY + cam.hfov*0.5 > maxPan) {                    cam.rotationY = maxPan - cam.hfov*0.5;                }            }            // rotationX is inverted from tilt, so apply our constraints in that space            var tilt:Number = -cam.rotationX;			if (maxTilt != 9999) {			    if (maxTilt == 90 && tilt > 90) {			        tilt = 90;			    } else if (tilt + cam.vfov*0.5 > maxTilt) {			        tilt = maxTilt - cam.vfov*0.5;		        }		    }		    if (minTilt != 9999) {			    if (minTilt == -90 && tilt < -90) {			        tilt = -90;			    } else if (tilt - cam.vfov*0.5 < minTilt) {			        tilt = minTilt + cam.vfov*0.5;		        }	        }	        cam.rotationX = -tilt;	    }				/**		* turns or zooms the camera the specified relative amount		* @param delta pan		* @param delta tilt		* @param delta zoom		*/		private function moveCamera(dp:Number, dt:Number, dz:Number):void		{			dt *= -1;						for (var i:int=0; i < spaces.length; i++)			{				var cam:PSCamera3D = PSCamera3D(spaces[i]["camera"]);				cam.rotationX += dt;				cam.rotationY += dp;				cam.fov += dz;                constrainCamera(cam);			}						//_worldDirty is set when camera or objects in scene have changed and need rendering.			_worldDirty = true;		}				protected var da:Number; // deltaAutorotate		protected var aah:Boolean;		protected var aaz:Boolean;				/**		* handles autorotating the camera		*/		private function autorotate():void		{			//da = findValueInXML("autorotatorIncrement", Number, 0.25);			for (var i:int=0; i < spaces.length; i++)			{				var cam:PSCamera3D = PSCamera3D(spaces[i].camera);				if (aah)				{					if (da > 0)					{						if (cam.rotationX > da ) { cam.rotationX -= da; }						else if (cam.rotationX < -da ) { cam.rotationX += da; }					}					else					{						if (cam.rotationX < da ) { cam.rotationX -= da; }						else if (cam.rotationX > -da ) { cam.rotationX += da; }					}				}				cam.rotationY += da;								if(aaz)				{					var vfov:Number = findValueInXML("cameraVFOV", Number, 60);				    var zoom:Number = findValueInXML("cameraZoom", Number, 0);         			var focus:Number = findValueInXML("cameraFocus", Number, 100);        		    if (zoom != 0.0) vfov = vfovForFocusZoom(focus, zoom);        			        			if (cam.fov < vfov)					{						cam.fov += vfovIncrement;												if (cam.fov > vfov)							cam.fov = vfov;					}					if (cam.fov > vfov)					{						cam.fov -= vfovIncrement;												if (cam.fov < vfov)							cam.fov = vfov;					}				}							}            constrainCamera(cam);			_worldDirty = true;				}				/**		* resize handler		*/		private function onResize(e:Event):void		{			_worldDirty = true;		}				/**		* main render loop.  will only render if the worldDirty has been set, or if there are any objects on the animated layer that need continuous re-rendering.		*/		private function doRender(e:Event=null):void		{			/* Check if any of the camera control flags are set and if so move the camera			*/						if (mouseIsDown || keyIsDown)			{				if (keyIsDown)				{					if ( up ) { startPoint.x = stage.mouseX, startPoint.y = stage.mouseY + keyIncrement ; }					if ( down ) { startPoint.x = stage.mouseX, startPoint.y = stage.mouseY - keyIncrement ; }					if ( left) { startPoint.x = stage.mouseX + keyIncrement, startPoint.y = stage.mouseY ; }					if ( right ) { startPoint.x = stage.mouseX - keyIncrement, startPoint.y = stage.mouseY ; }					if ( zoomin ) 					{ 						moveCamera(0, 0, vfovIncrement);												resting = false;					}					if ( zoomout ) 					{ 						moveCamera(0, 0, -vfovIncrement);												resting = false;					}				}				if (mouseIsDown || up || down || left || right)				{					// calculate new position changes					deltaPan = (deltaPan - (((startPoint.x - stage.mouseX) * sensitivity) * 0.00006));					deltaTilt = (deltaTilt + (((startPoint.y - stage.mouseY) * sensitivity) * 0.00006));				}			}			// motion is still over the threshold, so apply friction			if ( ( (deltaPan * deltaPan) + (deltaTilt * deltaTilt) ) > threshold ) 			{				// always apply friction so that motion slows AFTER mouse is up				deltaPan = (deltaPan * (1 - friction) );				deltaTilt = (deltaTilt * (1 - friction) );								moveCamera( deltaPan, deltaTilt, 0);								resting = false;			} 			else 			{ // motion is under threshold stop camera motion				if ( !mouseIsDown && !keyIsDown && !resting)				{						// motion is under threshold, stop and remove enter frame listener					deltaPan = 0;					deltaTilt = 0;										moveCamera(deltaPan, deltaTilt, 0)										changeQuality("resting");										if (_autorotatorOn)						restartAutorotatorTimer( findValueInXML("autorotatorRestartDelay", Number, 15000) );											resting = true;								}			}			if (isAutorotating) autorotate();						var i:int;						// check if anything has set the _worldDirty flag and if so render			if ( _worldDirty )			{				for (i = 0; i < spaces.length; i++)				{					PSRenderEngine(spaces[i].renderer).renderScene					( 					Scene3D(spaces[i].scene), 					PSCamera3D(spaces[i].camera), 					Viewport3D(spaces[i].viewport) 					);				}								_worldDirty = false;			}			else			{ // check if there are any animated materials that need continuous rendering.								var animatedLayer:ViewportLayer;				var animatedLayerCreated:Boolean = false;								for (i = 0; i < spaces.length; i++)				{					var staticLayer:ViewportBaseLayer = spaces[i].viewport.containerSprite;										var allObjects:Array = spaces[i].scene.objects as Array;					var numObjects:int = allObjects.length;					var allMaterials:Array = new Array();					for ( var j:int=0; j < numObjects; j++ )					{						var object:DisplayObject3D = DisplayObject3D( allObjects[ j ] );												var material:MaterialObject3D;												if (object.materials)						{/// obj.materials is the materialsList							for each( material in object.materials.materialsByName )							{								if ( material is MovieMaterial && (material as MovieMaterial).animated )								{									// put do3d in animated continuously rendering animatedLayer									if ( ! animatedLayerCreated )									{										animatedLayer = spaces[i].viewport.getChildLayer( object, true, false );																				animatedLayerCreated = true;									}									else									{										animatedLayer.addDisplayObject3D( object, false);									}									if ( staticLayer.hasDisplayObject3D(object) )									{										staticLayer.removeDisplayObject3D( object );									}								}								else								{									// put do3d in static layer which only renders with user input (camera motion)									staticLayer.addDisplayObject3D( object, false);									if (animatedLayer)									{										if ( animatedLayer.hasDisplayObject3D(object) )										{											animatedLayer.removeDisplayObject3D( object );										}									}								}																// break out of materials list loop								break;							}						}						else 						{// obj.material is the material							material = object.material;														if ( material is MovieMaterial && (material as MovieMaterial).animated )							{								// put do3d in animated continuously rendering animatedLayer								if ( ! animatedLayerCreated )								{									animatedLayer = spaces[i].viewport.getChildLayer( object, true, false );																		animatedLayerCreated = true;								}								else								{									animatedLayer.addDisplayObject3D( object, false);								}								if ( staticLayer.hasDisplayObject3D(object) )								{									staticLayer.removeDisplayObject3D( object );								}							}							else							{								// put do3d in static layer which only renders with user input (camera motion)								staticLayer.addDisplayObject3D( object, false);								if (animatedLayer)								{									if ( animatedLayer.hasDisplayObject3D(object) )									{									animatedLayer.removeDisplayObject3D( object );									}								}							}						}					}				}				if ( animatedLayer )				{					// render the animated layer.					for (i = 0; i < spaces.length; i++)					{						PSRenderEngine(spaces[i].renderer).renderLayers						( 							Scene3D(spaces[i].scene), 							PSCamera3D(spaces[i].camera), 							Viewport3D(spaces[i].viewport),							[ animatedLayer ]						);					}				}								// end else render animated objects			}		}						/**		* main interactiveScene3DEvent handler for all PV3D 'display objects'.  action executed is set in the descriptor XML.		*/		private function interactionScene3DEventHandler(e:InteractiveScene3DEvent=null):void		{			var name:String = e.target.name;						trace("PS:" + name + "." + interactionEquivalents[e.type] );						execute( settings..*.(hasOwnProperty("@id") && @id == name).attribute( interactionEquivalents[e.type] ).toString() );		}				/**		* mouse event handler for normal display objects. action executed is set in the descriptor XML.		*/		private function mouseEventHandler(e:MouseEvent=null):void		{			//trace("mouse event", e.target.name, e.type);						var name:String = e.target.name;									execute( settings..*.(hasOwnProperty("@id") && @id == name).attribute( interactionEquivalents[e.type] ).toString() );					}				/**		* handles cursor image if applicable.		*/		private function cursorHandler(e:InteractiveScene3DEvent):void		{			switch (e.type)			{				case (InteractiveScene3DEvent.OBJECT_OVER):					dispatchBroadcast(BroadcastEvent.HIDE_CURSOR);					//parent.dispatchEvent( new BroadcastEvent(BroadcastEvent.HIDE_CURSOR) );					Sprite( getSpaceByName(currentSpace).viewport ).buttonMode = true;					Sprite( getSpaceByName(currentSpace).viewport ).useHandCursor = true;										break;				case (InteractiveScene3DEvent.OBJECT_OUT):					//parent.dispatchEvent( new BroadcastEvent(BroadcastEvent.SHOW_CURSOR) );					dispatchBroadcast(BroadcastEvent.SHOW_CURSOR);					Sprite( getSpaceByName(currentSpace).viewport ).buttonMode = false;					Sprite( getSpaceByName(currentSpace).viewport ).useHandCursor = false;										break;			}		}				/**		* handles tooltip interaction with the interface if applicable.		*/		private function tooltipHandler(e:InteractiveScene3DEvent):void		{			switch (e.type)			{				case (InteractiveScene3DEvent.OBJECT_OVER):					var objName:String = e.target.name.toString();					dispatchBroadcast					(						BroadcastEvent.SHOW_TOOLTIP, 						"name="+objName, 						"toolTip="+settings..*.(hasOwnProperty("@id") && @id == objName).attribute( "toolTip" ),						"x="+mouseX,						"y="+mouseY					);					break;				case (InteractiveScene3DEvent.OBJECT_OUT):					dispatchBroadcast(BroadcastEvent.HIDE_TOOLTIP);					break;			}		}		//functions called by XML code hooks		/**		* dispatches a generic event on ModuleLoader for communication between moduleLoader's layers.  		* @param String event type that is being listened for.		* @param Further arguments are stored in the 'info' object to allow sending information along with the event e.g. tooltip text.		* 		*/		public function dispatchBroadcast(type:String, ... args):void		{			trace("PS: dispatchBroadcast", type, args );			var obj:Object = new Object();						var len:int = args.length;			for (var i:int=0; i<len; i++)			{				var arr:Array = args[i].split("=");				obj[arr[0]] = arr[1];			}			if (moduleLoader)				moduleLoader.dispatchEvent( new BroadcastEvent(type, obj) );		}				/**		* key down event handler		*/		public function keyDown(direction:String):void		{			trace("PS: go:"+direction);						var e:KeyboardEvent = new KeyboardEvent(KeyboardEvent.KEY_DOWN);						e.keyCode = Keyboard[direction.toUpperCase()]						//stage.dispatchEvent(e)			keyDownEvent(e);		}				/**		* key up event handler		*/		public function keyUp(direction:String):void		{						trace("PS: stop:"+direction);						var e:KeyboardEvent = new KeyboardEvent(KeyboardEvent.KEY_UP);						e.keyCode = Keyboard[direction.toUpperCase()]						//stage.dispatchEvent(e)			keyUpEvent(e);		}				/**		* fullscreen toggle function. dispatches BroadcastEvent.ENTER_FULLSCREEN and EXIT_FULLSCREEN		*/		public function toggleFullscreen():void		{			switch(stage.displayState) 			{				case "normal":					stage.displayState = "fullScreen";					dispatchBroadcast(BroadcastEvent.ENTER_FULLSCREEN);					break;				case "fullScreen":				default:					stage.displayState = "normal";					dispatchBroadcast(BroadcastEvent.EXIT_FULLSCREEN);					break;			}		}				/**		* toggles autorotator.  if toggled on, it will start autorotation immediately.		*/		public function toggleAutorotator():void		{			trace("PS: toggleAutorotator");						autorotatorOn = !autorotatorOn;						if ( autorotatorOn )				startAutorotatorNow();			// 			if (isAutorotating)// 			{// 				autorotatorOn = false;// 			}// 			else// 			{// 				startAutorotatorNow();// 			}		}				/**		* parsers a String tween from the XML, deprecated, and SpinControl will only use XML tweens		* @see tweenXML		*/		public function tween(str:String):void		{			trace("PS:tween:"+str);						var o:Object = new Object();						var pieces:Array = str.split(" ");						var targetStr:String;			var targetObj:Object;			var property:String;			var targetGlob:String = pieces[0];				var targetArr:Array = targetGlob.split(".");				if (targetArr.length == 2)				{					targetStr = targetArr[0];					property = targetArr[1];				}				else				{					if (targetArr[0] == "lastSpace")					{						targetObj = getSpaceByName( lastSpace )[ targetArr[1] ];					}					else if(targetArr[0] == "currentSpace")					{						targetObj = getSpaceByName( currentSpace )[ targetArr[1] ];					}					else if(targetArr[0] == "loadingSpace")					{						targetObj = getSpaceByName( loadingSpace )[ targetArr[1] ];					}					else					{						targetObj = getSpaceByName( targetArr[0] )[ targetArr[1] ];					}										property = targetArr[2];				}							var direction:String = pieces[1];			var endValue:* = pieces[2];			endValue = (endValue.indexOf("'") == -1) ? Number(endValue) : String( endValue.substring(1,pieces[2].lastIndexOf("'")) ) ;				o[property] = endValue;						// pieces[3] = "over"			var time:Number = Number( pieces[4] );			// pieces[5] = "seconds"						// pieces[6] = "using"			var easingGlob:String = pieces[7]  || "Expo.easeOut"; 							var easingArr:Array = easingGlob.split(".");				var easeClass:String = easingArr[0];				var easeFunction:String = easingArr[1];			// pieces[8] = "then"			// pieces[9] = "do"			var afterGlob:String = pieces[10] || ""; 			if (afterGlob != "")			{				o.onComplete = execute;				o.onCompleteParams = [afterGlob];			}						var ec:Class;			var ef:Function;			if ( getDefinitionByName("gs.easing."+easeClass) )			{				ec = getDefinitionByName("gs.easing."+easeClass) as Class;				ef = ec.hasOwnProperty(easeFunction) ? ec[easeFunction] : ec.easeOut; 			}			else			{				trace("PS: tween err: can't find the given tween class, using default");				ec = getDefinitionByName("gs.easing.Expo") as Class;				ef = ec.easeOut;			}			o.ease = ef as Function;			o.overwrite = false;						// camera and viewport are specials, otherwise it will use getDisplayObject3dByName()			if (targetStr)			{				var i:int;				switch (targetStr)				{					case ("allCameras"):						dirtyWorld();						o.onUpdate = dirtyWorld;						for (i=0; i < spaces.length; i++)						{							var cam:PSCamera3D = PSCamera3D(spaces[i].camera);							TweenLite[direction].call(TweenLite, cam, time, o ); 						}						break;					case ("allViewports"):						for (i=0; i < spaces.length; i++)						{							var vp:Viewport3D = Viewport3D(spaces[i].viewport);							TweenLite[direction].call(TweenLite, vp, time, o ); 						}						break;					default:						dirtyWorld();						o.onUpdate = dirtyWorld;						TweenLite[direction].call(TweenLite, getDisplayObject3dByName(targetStr), time, o )						break;								}			}			else if(targetObj)			{				if (targetObj is PSCamera3D)				{					dirtyWorld();											TweenLite[direction].call(TweenLite, PSCamera3D(targetObj), time, o ); 				}				else if (targetObj is Viewport3D)				{					TweenLite[direction].call(TweenLite, Viewport3D(targetObj), time, o ); 				}			}		}				/**		* parses an XML tween from the XML		*/		public function tweenXML(str:String):void		{			var xml:XML = findXMLNode(str);						if ( !xml)				return;						var o:Object = new Object();						var targetGlob:String = xml.@target.toString();			var property:String = xml.@property.toString();			var direction:String = xml.@direction.toString();			var endValue:*  = xml.@endValue; 			endValue = (xml.@endValue.indexOf("'") == -1) ? Number(endValue) : String( xml.@endValue.substring(1,xml.@endValue.lastIndexOf("'")) ) ;			var time:Number = Number(xml.@time);			var easeClass:Class  = getDefinitionByName("gs.easing."+xml.@easeClass) as Class;			var easeFunction:Function = easeClass[xml.@easeFunction.toString()];			var onComplete:Function = execute;			var onCompleteParams:Array = [ xml.@onComplete.toString() ];			//var onUpdate:Function = dirtyWorld; 			//TweenLite.from( getSpaceByName(currentSpace).viewport, 5, {rotation:90, onComplete:execute, onCompleteParams:["onTransitionEnd"], overwrite:false} )			o[property] = endValue;			o.ease = easeFunction;			o.onComplete = (onCompleteParams[0]) ? onComplete : null;			o.onCompleteParams = (onCompleteParams[0]) ? onCompleteParams : null;			o.overwrite = false; 						var targetStr:String;			var targetObj:Object;						var targetArr:Array = targetGlob.split(".");			if (targetArr.length == 1)			{				targetStr = targetArr[0];								var i:int;				switch (targetStr)				{					case ("allCameras"):						dirtyWorld();						o.onUpdate = dirtyWorld;						for (i=0; i < spaces.length; i++)						{							var cam:PSCamera3D = PSCamera3D(spaces[i].camera);							TweenLite[direction].call(TweenLite, cam, time, o ); 						}						break;					case ("allViewports"):						for (i=0; i < spaces.length; i++)						{							var vp:Viewport3D = Viewport3D(spaces[i].viewport);							TweenLite[direction].call(TweenLite, vp, time, o ); 						}						break;					default:						dirtyWorld();						o.onUpdate = dirtyWorld;						TweenLite[direction].call(TweenLite, getDisplayObject3dByName(targetStr), time, o )						break;				}			}			else			{				if (targetArr[0] == "lastSpace")				{					targetObj = getSpaceByName( lastSpace )[ targetArr[1] ];				}				else if(targetArr[0] == "currentSpace")				{					targetObj = getSpaceByName( currentSpace )[ targetArr[1] ];				}				else if(targetArr[0] == "loadingSpace")				{					targetObj = getSpaceByName( loadingSpace )[ targetArr[1] ];				}				else				{					targetObj = getSpaceByName( targetArr[0] )[ targetArr[1] ];				}								if (targetObj is PSCamera3D)				{					dirtyWorld();					o.onUpdate = dirtyWorld;					TweenLite[direction].call(TweenLite, PSCamera3D(targetObj), time, o ); 				}				else if (targetObj is Viewport3D)				{					TweenLite[direction].call(TweenLite, Viewport3D(targetObj), time, o ); 				}			}		}				/**		* sets worldDirty		*/		private function dirtyWorld():void		{			_worldDirty = true;		}				/**		* changes a property immediately, unlike a tween, which changes it over time.		* @see tweenXML		*/		public function change( ... args):void		{			trace("PS: change:", args);									var arr:Array;			var arrLeft:Array;			var target:DisplayObject3D 			var propClass:Class;			var className:String;			var len:int = args.length;			for (var i:int=0; i<len; i++)			{				arr = args[i].split("=");								arrLeft = arr[0].split(".");								target = getDisplayObject3dByName( arrLeft[0] );				if (target)				{					if ( target.hasOwnProperty( arrLeft[1] ) )					{						className = getQualifiedClassName( target[ arrLeft[1] ] ) ;						propClass = getDefinitionByName( className ) as Class;												if ( className == "Boolean" ) 							target[ arrLeft[1] ] = StringTo.bool( arr[1] );						else if ( className == "Number" || className == "int" || className == "uint")							target[ arrLeft[1] ] = Number( arr[1] );						else							target[ arrLeft[1] ] = propClass( arr[1] );					}				}			}						_worldDirty = true;					}		// 		public function set(str:String):void// 		{// 			trace("PS: set: ", str);// 			// 			var leftSide:String = str.slice(0, str.indexOf("=") );// 			// 			var rightSide:String = str.slice( str.indexOf("=")+1, str.length );// 			// 			var leftArray:Array = leftSide.split(".");// 			// 			var rightArray:Array = rightSide.split(",");// 			// 			var rightObject:Object = new Object();// 			// 			for (var i:int=0; i< rightArray.length; i++)// 			{// 				var name:String = rightArray[i].slice(0, rightArray[i].indexOf(":") );// 				var value:String = rightArray[i].slice(rightArray[i].indexOf(":")+1 );// 				// 				rightObject[name] = value;// 			}// 			// 			// 		}				///  This function is deprecated// 		public function loadSpaceAndInterface(name:String):void// 		{// 			trace("PS: loadSpaceAndInterface:"+name); // 			// 			if (bulkLoader.isRunning)// 			{// 				bulkLoader.removeAll();// 			}// 			// 			for each (var xml:XML in findXMLNode(name).children() )// 			{// 				for each (var mat:XML in xml.file)// 				{// 					bulkLoader.add(mat.toString(), { type:"image", weight: (mat.@weight || 10) });// 				// 					if (xml.name().localName.toString() != "bitmap")// 					{// 						bulkLoader.get(mat.toString()).addEventListener(Event.COMPLETE, onSingleItemLoaded, false, 100, true);// 					}// 				}// 			}// 			// 			loadingSpace = name;// 			// 			bulkLoader.start();// 			// 		}						/**		* initiates the loading of a new space. 		* @param name	A String with the name of the space in the descriptor XML to load.		*/		public function loadSpace(name:String):void		{			trace("PS: loadSpace:"+name);						var wait:Boolean = false;			var waitStreams:int = 0;						if (bulkLoader.isRunning)			{				bulkLoader.removeAll();			}						var nodeToLoad:XML = findXMLNode(name);						for each (var mat:XML in nodeToLoad..file)			{ 				bulkLoader.add(mat.toString(), { type:"image", weight: (mat.@weight || 10) });								bulkLoader.get(mat.toString()).addEventListener(Event.COMPLETE, onSingleItemLoaded, false, 100, true);								wait = true;			}						for each (var tile:XML in nodeToLoad..tile)			{ 				bulkLoader.add(tile.toString(), { type:"image", weight: (tile.@weight || 10) });								wait = true;			}						qtvrCount = 0;						for each (var mov:XML in nodeToLoad..mov)			{				bulkLoader.add(mov.toString(), { type:"xml", weight: (mov.@weight || 10) });								bulkLoader.get(mov.toString()).addEventListener(Event.COMPLETE, qtvrXMLLoaded, false, 100, true);								qtvrCount += 1;								wait = true;			}						var numStreamMaterials:int = 0;			for each (var stream:XML in nodeToLoad..stream)			{			    if (loadStreamItem(stream)) {			        waitStreams++;			    }			    numStreamMaterials++;		    }		    if (waitStreams > 0) wait = true;						loadingSpace = name;						dispatchBroadcast(BroadcastEvent.LOADING_SPACE, "loadingSpace="+name);						if (wait)				bulkLoader.start();			else				onAllLoaded();					}				/**		* loads a single stream item		* @param stream	XML object with stream properties.		*/		private function loadStreamItem(stream:XML):Boolean		{			Security.loadPolicyFile( stream.@policyFile.toString() );						var video:VideoDisplaySprite = new VideoDisplaySprite();            video.source = stream.toString();			video.volume = 1.0;			video.addEventListener("materialAvailable", onStreamMaterialLoaded);			video.play(); // first frame doesn't seem to appear if we use load().            var material:VideoPlayerMaterial = video.material;            if (material) {                // trigger the listener                video.dispatchEvent(new Event("materialAvailable"));                return false;            }            return true;    // wait for material	    }				/**		* removes the last loaded space, if applicable		*/		public function removeLastSpace():void		{			if (viewports.numChildren > 1 )			{				trace("PS: removeLastSpace:"+lastSpace);								var spaceToRemove:Object = getSpaceByName(lastSpace);				if (!spaceToRemove) return;								if (spaceToRemove.viewport.interactiveSceneManager)					spaceToRemove.viewport.interactiveSceneManager.removeEnterFrameListener();								viewports.removeChild( viewports.getChildByName( lastSpace ) );								spaceToRemove.viewport.destroy();								var objects:Array = spaceToRemove.scene.objects;				var len:int = objects.length;				for ( var j:int=0; j < len; j++ )				{					var obj:DisplayObject3D = objects[ j ];										if (obj.materials)					{						for each(  var mo3d:MaterialObject3D in obj.materials.materialsByName )						{							if (mo3d is BitmapMaterial)							{								BitmapMaterial(mo3d).destroy();							}							else { mo3d.destroy(); }						}					}					else					{ // object has only one material accessed this way:						var mat:MaterialObject3D = obj.material;						if (mat is BitmapMaterial)						{							BitmapMaterial(mat).destroy();						}						else { mat.destroy(); }					}										obj = null;				}								spaceToRemove.renderer.destroy();								spaceToRemove.scene = null;								spaceToRemove.camera = null;								if (spaceToRemove.stats)					spaceToRemove.stats = null;								spaces.splice(spaces.indexOf(spaceToRemove), 1);								spaceToRemove = null;							}			else trace("PS: removeLastSpace: there is no last space to remove"); 		}				/**		* opens a URL in the browser		* @param url	String value of the absolute URL to open		* @param window	String value of browser window name to open, defaults to '_blank'		*/		public function openURL(url:String, window:String="_blank"):void		{ trace("openURL:", url, window);			navigateToURL( new URLRequest(url), window);		}				/**		* helper function that creates new PV3D space objects: viewport, scene, camera, renderer.		*/		private function instantiateNewSpace():int		{			spaces.push( new Object() );						var idx:uint = spaces.length-1;						//Viewport3D(viewportWidth:Number = 640, viewportHeight:Number = 480, autoScaleToStage:Boolean = false, interactive:Boolean = false, autoClipping:Boolean = true, autoCulling:Boolean = true)			var viewport:Viewport3D = new Viewport3D( 640, 480, true, false, true, true);						viewport.name = currentSpace;						spaces[idx].viewport = viewport;						var scene:Scene3D = new Scene3D();						spaces[idx].scene = scene;						var camera:PSCamera3D = new PSCamera3D();			camera.x = 0;			camera.y = 0;			camera.z = 0;						var vp:Rectangle = new Rectangle();			vp.width = viewport.viewportWidth;			vp.height = viewport.viewportHeight;			camera.update(vp);						spaces[idx].camera = camera;						var renderer:PSRenderEngine = new PSRenderEngine();						spaces[idx].renderer = renderer;						spaces[idx].name = currentSpace;						if ( StringTo.bool(settings.spaces.@statistics) || false )			{				var stats:StatsView = new StatsView( spaces[idx].renderer );							spaces[idx].stats = stats;							viewport.addChild(stats);			}						return idx;		}				/**		* gets a loaded space by name		* @param name	A String value with the name of the space to retrieve.		* @return Object		*/		public function getSpaceByName(name:String):Object		{			var i:int = 0;						while (i<spaces.length)			{				if (spaces[i].name == name) 				{ 					return spaces[i]; 				}				i++			}			return null;		}				/**		* gets a PV3d DisplayObject3D by name		* @param	name	A String with the name		* @return	DisplayObject3D		*/		public function getDisplayObject3dByName(name:String):DisplayObject3D		{			for (var i:int = 0; i < spaces.length; i++)			{				var objs:Array = spaces[i].scene.objects as Array;				var num:int = objs.length;				for ( var j:int=0; j < num; j++ )				{					var obj:DisplayObject3D = DisplayObject3D(objs[ j ]);										if (obj.name == name) 					{						return obj;					}				}			}			return null;		}						///////////////////////////////////////////////////////////		// CAMERA CONTROLLER				private var startPoint:Point = new Point( 0,0 );				private var deltaPan:Number = 0;		private var deltaTilt:Number = 0;		/**		* sensitivity of camera to mouse motion		*/		public var sensitivity:Number = 60;				/**		* friction of camera after mouse is up		*/		public var friction:Number = 0.3;				/**		* camera pan / tilt threshold at which motion jumps to 0		*/		public var threshold:Number = 0.0001;				/**		* delta VFOV value that will be used for key zooming		*/		public var vfovIncrement:Number = 1.0;				/**		* delta pan / tilt value for key panning / tilting		*/		public var keyIncrement:Number = 75;				private var autorotatorTimer:Timer;		private var _autorotatorDelay:Number = 15000;		private var _autorotatorRestartDelay:Number = 15000;		private var _autorotatorOn:Boolean = true;				private var resting:Boolean = true;				private var mouseIsDown:Boolean = false;		private var keyIsDown:Boolean = false;				/**		* flag set if camera is autorotating		*/		public var isAutorotating:Boolean = false;				private var up:Boolean = false;		private var down:Boolean = false;		private var left:Boolean = false;		private var right:Boolean = false;		private var zoomin:Boolean = false;		private var zoomout:Boolean = false;				/**		* helper to initialize stage event listeners for camera <-> mouse / camera <-> key interaction		* @param	autorotation	A Boolean whether to turn on the autorotator		* @param	autorotatorDelay	A Number indicating delay before autorotation begins, in milliseconds, defaults to 15000.		*/		private function initCameraController( autorotator:Boolean = true, autorotatorDelay:Number = 15000 ):void		{			//this._parent = _parent;			this.autorotatorDelay = autorotatorDelay			stage.addEventListener( MouseEvent.MOUSE_DOWN, mouseDownEvent, false, 100, true );			stage.addEventListener( MouseEvent.MOUSE_UP, mouseUpEvent, false, 0, true );			stage.addEventListener( MouseEvent.MOUSE_WHEEL, mouseWheelEvent, false, 100, true );			stage.addEventListener( Event.DEACTIVATE, mouseUpEvent, false, 0, true );			stage.addEventListener( KeyboardEvent.KEY_DOWN, keyDownEvent, false, 100, true );			stage.addEventListener( KeyboardEvent.KEY_UP, keyUpEvent, false, 0, true);						// start the autorotator			_autorotatorOn = autorotator;			if (autorotator)			{				setUpAutorotator();			}		}				/**		* helper function that allows for filtering of mouse clicks based on whether the click target is of the Application class		* @return	Boolean		*/		private function isApplication(target:Object):Boolean		{			if ( ApplicationDomain.currentDomain.hasDefinition("mx.core.Application") )			{				if ( target is Class( ApplicationDomain.currentDomain.getDefinition("mx.core.Application") ) )				{					return true;				}			}						return false;		}				/**		* mouse down handler for camera interaction		*/		protected function mouseDownEvent( event:MouseEvent ):void		{ 			if ( (event.target is ViewportBaseLayer) || (event.target is ViewportLayer) || isApplication(event.target) )			{				mouseIsDown = true;								startPoint.x = stage.mouseX;				startPoint.y = stage.mouseY;								changeQuality("accelerating");								stopAutorotatorNow();			}		}				/**		* mouse wheel handler for camera interaction		*/		protected function mouseWheelEvent( event:MouseEvent ):void		{	        var dz:Number = event.delta;            moveCamera(0, 0, dz);	    }				/**		* key handler for camera interaction		*/		protected function keyDownEvent( event:KeyboardEvent ):void		{ 			switch( event.keyCode )			{			case Keyboard.UP:				up = true; 				startKeyMovement();			break;				case Keyboard.DOWN:				down = true;				startKeyMovement();			break;				case Keyboard.LEFT:				left = true;				startKeyMovement();			break;				case Keyboard.RIGHT:				right = true;				startKeyMovement();			break;			case Keyboard.SHIFT:				zoomin = true;				startKeyMovement();			break;			case Keyboard.CONTROL:				zoomout = true;				startKeyMovement();			break;			}		}				/**		* helper key camera interaction function		*/		private function startKeyMovement():void		{			stopAutorotatorNow();						keyIsDown = true;						changeQuality("accelerating");		}				/**		* mouse up camera interaction handler		*/		protected function mouseUpEvent( event:Event ):void		{			mouseIsDown = false;						changeQuality("decelerating");		}				/**		* key up camera interaction handler.		*/		protected function keyUpEvent(event:KeyboardEvent):void		{			switch( event.keyCode )				{				case Keyboard.UP:					up = false;				break;					case Keyboard.DOWN:					down = false;				break;					case Keyboard.LEFT:					left = false;				break;					case Keyboard.RIGHT:					right = false;				break;				case Keyboard.SHIFT:					zoomin = false;				break;				case Keyboard.CONTROL:					zoomout = false;				break;				}			if ( !up && !down && !left && !right && !zoomin && !zoomout )			{				keyIsDown = false;								changeQuality("decelerating");			}		}				public function set autorotatorDelay(autorotatorDelay:Number):void		{			if (autorotatorTimer != null)			{				autorotatorTimer.delay = autorotatorDelay;			}			this._autorotatorDelay = autorotatorDelay;		}		public function get autorotatorDelay():Number		{			return _autorotatorDelay;		}				public function set autorotatorRestartDelay(autorotatorRestartDelay:Number):void		{			this._autorotatorRestartDelay = autorotatorRestartDelay;		}		public function get autorotatorRestartDelay():Number		{			return _autorotatorRestartDelay;		}				/**		* instantiates autorotator timer.		*/		private function setUpAutorotator():void		{			autorotatorTimer = new Timer(autorotatorDelay);			autorotatorTimer.addEventListener("timer", startAutorotatorNow, false, 0, true);			//restartAutorotatorTimer();		}				/**		* handles restarting the autorotator timer		*/		public function restartAutorotatorTimer(delay:Number=15000):void		{			if (_autorotatorOn)			{				stopAutorotatorNow();				autorotatorTimer = new Timer(delay);				autorotatorTimer.addEventListener("timer", startAutorotatorNow, false, 0, true);				autorotatorTimer.start();			}		}				/**		* immediately starts camera autorotation		*/		public function startAutorotatorNow(e:TimerEvent=null):void		{			if (autorotatorTimer)			{				autorotatorTimer.stop();				autorotatorTimer.reset();			}						isAutorotating = true;		}				/**		* immediately stops camera autorotation		*/		public function stopAutorotatorNow():void		{			if (autorotatorTimer)			{				autorotatorTimer.stop();				autorotatorTimer.reset();				autorotatorTimer.removeEventListener("timer", startAutorotatorNow);				autorotatorTimer = null;			}						isAutorotating = false;		}						public function set autorotatorOn(value:Boolean):void		{			if (_autorotatorOn != value)			{				_autorotatorOn = value;								if (value)				{					restartAutorotatorTimer();					dispatchBroadcast(BroadcastEvent.AUTOROTATION_ON);				}				else				{					stopAutorotatorNow();					dispatchBroadcast(BroadcastEvent.AUTOROTATION_OFF);				}			}		}				public function get autorotatorOn():Boolean		{			return _autorotatorOn;		}								/////////////////////////////////////////////////////																														//tools				/**		* utility function used by hotspot function to point plane at the origin (camera)		*/		private function pinToSphere(r:Number, p:Number, t:Number):Number3D		{			var pr:Number	= (-1*(p - 90)) * (Math.PI/180); 			var tr:Number	= t * (Math.PI/180);			var xc:Number = r * Math.cos(pr) * Math.cos(tr);			var yc:Number = r * Math.sin(tr);			var zc:Number = r * Math.sin(pr) * Math.cos(tr);						var n:Number3D = new Number3D();			n.x = xc;			n.y = yc;			n.z = zc;			return n;		}				// XML Tools		/**		* utility function that executes a function based on the XML descriptor 		* @param 	name	A string with the XML code hook name		* @param	checkCurrentSceneFirst	optional Boolean flag to check the current scene's XML first		*/		private function XMLCodeHook(name:String, checkCurrentSceneFirst:Boolean=true):void		{				var attr:String;						if (checkCurrentSceneFirst)			{				attr = findValueInXML(name, String, "");			}			else attr = settings.spaces.attribute(name).toString();						if ( attr != null && attr != "")			{				execute(attr);			}		}				/**		* generic function that executes a function either on this object or from the XML		* @param	name	A String with the name of the function to execute		*/		public function execute(attr:String):void		{						if ( attr != null && attr != "")			{				trace("PS: execute: "+attr);								var lines:Array = attr.split(";");				for (var i:uint = 0; i < lines.length; i++)				{						var func:String;					var colonIdx:int = lines[i].indexOf(":");					if ( colonIdx != -1 )					{						//var action:Array = lines[i].split(":");						func = lines[i].substring( 0, colonIdx );						func = StringUtil.trim(func);												var argStr:String = lines[i].substring( colonIdx+1 );						var args:Array = argStr.split(",");						for each (var s:String in args)						{							s = StringUtil.trim(s);						}												if( this.hasOwnProperty(func) )						{							(this[func] as Function).apply(this, args);						}// 						else if ( CustomActions )// 						{// 							if( CustomActions.hasOwnProperty(func) )// 							{// 								(CustomActions[func] as Function).apply(CustomActions, args);// 							}// 						}						else						{							executeXMLFunction(func);						}					}					else 					{						func = lines[i];						func = StringUtil.trim(func);						if( this.hasOwnProperty(func) )						{							(this[func] as Function).call(this);						}// 						else if ( CustomActions )// 						{// 							if( CustomActions.hasOwnProperty(func) )// 							{// 								(CustomActions[func] as Function).call(CustomActions);// 							}// 						}						else						{							executeXMLFunction(func);						}											}				}			}		}				/**		* function that executes a function specified in the XML		* @param	name	A String with the name of the function to execute.		*/		private function executeXMLFunction(str:String):void		{			trace("PS: executeXMLFunction:"+str);						var strArr:Array = str.split(".");						var functionScope:String;			var functionName:String;			if (strArr.length != 2) 			{ // function is NOT identified by a scope, search each space then spaces.				functionName = findValueInXML( str, String, "" );				execute( functionName );			}			else			{				functionScope = strArr[0];				functionName = strArr[1];								var xmlFunction:String				if (functionScope == "spaces")				{					xmlFunction = settings.spaces.attribute( functionName )				}				else				{					var scope:XML = findXMLNode( functionScope );					xmlFunction = scope.@[functionName];				}								if (xmlFunction != "" && xmlFunction != null)					execute( xmlFunction );			}		}				// this is specifically for drop down controller components		/**		* Special purpose function for populating ComboBox's with space ids and labels.		*/		public function getSpacesAndLabels():Array		{			var retArr:Array = new Array();			var node:XML;			var label:String;			var id:String;			var entry:Object = new Object();			for each (node in settings.spaces.*)			{				label = node.@label || node.@id;				id = node.@id;								entry.label = label;				entry.id = id;								retArr.push( entry );			}						return retArr;		}				//search in child node first, then in settings node		/**		* utility function that gets an XML node in the descriptor XML by name		* @param	name	A String with the name of the node		*/		private function findXMLNode(name:String):XML		{			var node:XML;			for each (node in settings..*)			{				if (node.@id == name)				{					break;				}			}			if (node.@id == name)				return node;			else return null;		}				/**		* utility function that looks for a value by name in the XML first checks the local space node, then the spaces node then returns the default value		* @param	name	A String with the name of the value to find		* @param	ReturnClass	The class to cast the value as		* @param	default	The default value to return if not found.		*/		private function findValueInXML(name:String, ReturnClass:Class, def:*):*		{			// check the currentSpace node for the value first			var cs:XML = findXMLNode(currentSpace);			if (cs)			{				if ( cs.attribute(name).toString().length != 0 )				{ 					if ( ReturnClass != Boolean )					{ 						return ReturnClass( cs.attribute(name) );					}					else					{ 						return ReturnClass( StringTo.bool(cs.attribute(name) ) );					}				}			}			// check the spaces node next			if ( settings.spaces.attribute(name).toString().length != 0 )			{ 				if ( ReturnClass != Boolean )				{					return ReturnClass( settings.spaces.attribute(name) );				}				else				{					return ReturnClass( StringTo.bool(settings.spaces.attribute(name)) );				}			}			else return ReturnClass(def);		}				private var zExpo:Function = Expo.easeIn;		private var zBack:Function = Back.easeIn;		private var zBounce:Function = Bounce.easeIn;		private var zCirc:Function = Circ.easeIn;		private var zCubic:Function = Cubic.easeIn;		private var zElastic:Function = Elastic.easeIn;		private var zQuad:Function = Quad.easeIn;		private var zQuart:Function = Quart.easeIn;		private var zQuint:Function = Quint.easeIn;		private var zSine:Function = Sine.easeIn;		private var zLinear:Function = Linear.easeIn;	}}